import { Meta } from '@storybook/addon-docs';

<Meta title="Concepts/Developer/Web Components Interop/Using Fluent React with Web Components" />

# Using Fluent React with Web Components

Fluent React v9's extensible architecture and use of web platform features like CSS custom properties
allow it to be extended to work well with [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components),
particularly [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM).

It is necessary to modify Fluent React v9's default behavior to better interoperate with Web Components. The
implementation lives in the [Fluent UI Contrib repository](https://github.com/microsoft/fluentui-contrib).

## Rendering Fluent React in shadow DOM

Fluent React components can be rendered inside shadow DOM with styles being set on the shadow root's `adoptedStyleSheets`
property.

```tsx
import { root } from '@fluentui-contrib/react-shadow';
import { FluentProvider, webLightTheme, Button } from '@fluentui/react-components';

<FluentProvider theme={webLightTheme}>
  {/* This is the shadow root */}
  <root.div>
    <Button>Fluent React Button in shadow DOM</Button>
  </root.div>
</FluentProvider>;
```

In the above example, note that `FluentProvider` sits outside the shadow DOM. When `FluentProvider` is inside the shadow
DOM styling/theming will not work as expected.

> ⚠️ `FluentProvider` must be in the light DOM for this method to work.

```tsx
// ❌ This will not render correctly, for example purposes only ❌
import { root } from '@fluentui-contrib/react-shadow';
import { FluentProvider, webLightTheme, Button } from '@fluentui/react-components';

/* This is the shadow root */
<root.div>
  <FluentProvider theme={webLightTheme}>
    <Button>Fluent React Button in shadow DOM</Button>
  </FluentProvider>
</root.div>;
```

To render a provider inside shadow DOM, use `ThemelessFluentProvider` instead.

## `ThemelessFluentProvider`

`ThemelessFluentProvider` is a special version of `FluentProvider` that is designed to integrate with
shadow DOM. It supports the same features as `FluentProvider` except:

1. It does not insert styles into the DOM
2. It removes the themeing related contexts

Because `ThemelessFluentProvider` provides no styles for the theme you must provide them yourself.
This means creating a CSS rule that defines all the CSS custom properties needed for a Fluent theme.
The theme can be created in React or another part of the application. The CSS custom properties just need to be
defined in such a way that they will pierce the shadow DOM (e.g., on the `:root` selector).

```tsx
import { createCSSStyleSheetFromTheme, ThemelessFluentProvider } from '@fluentui-contrib/react-themeless-provider';
import { root } from '@fluentui-contrib/react-shadow';
import { webLightTheme, Button } from '@fluentui/react-components';

// Create theme styles outside of React component rendering
const themeSheet = createCSSStyleSheetFromTheme(':root', webLightTheme);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, themeSheet];

// Render Fluent components
const ShadowDOMApp = () => {
  return (
    <ThemelessFluentProvider>
      {/* This is the shadow root */}
      <root.div>
        <Button>Fluent React Button in shadow DOM</Button>
      </root.div>
    </ThemelessFluentProvider>
  );
};
```

## Keyboarding

Fluent React uses [Tabster](https://tabster.io/) to control keyboarding and it must be able to "see" the entire DOM
of a page to function correctly. Since shadow DOM "hides" parts of the DOM from Tabster, you must opt into
shadow DOM support when using shadow DOM and Tabster together.

```tsx
import { useShadowDOMSupport } from '@fluentui-contrib/pierce-dom';
import { root } from '@fluentui-contrib/react-shadow';
import { createCSSStyleSheetFromTheme, ThemelessFluentProvider } from '@fluentui-contrib/react-themeless-provider';
import { webLightTheme, Button } from '@fluentui/react-components';

const themeSheet = createCSSStyleSheetFromTheme(':root', webLightTheme);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, themeSheet];

const AppComponent = () => {
  // This must be called _before_ you render any Fluent React controls
  useShadowDOMSupport();
  <ThemelessFluentProvider>
    {/* This is the shadow root */}
    <root.div>
      <Button>First Button</Button>
      <Button>Second Button</Button>
    </root.div>
  </ThemelessFluentProvider>;
};
```

# Insertion Point

Fluent uses Griffel for authoring styles and it provides the [mergeClasses](https://griffel.js.org/react/api/merge-classes)
function for controlling style specificity. `mergeClasses` can accept any CSS class name but it only performs rule ordering
for class names generated by Griffel. This can lead to CSS specificity issues when you want to use CSS classes that were
not generated by Griffel (for example, application utility classes).

For those cases, use the insertion point API to control the specificity of styles via document order.

```tsx
import { createRoot } from '@fluentui-contrib/react-shadow';
import { createCSSStyleSheetFromTheme, ThemelessFluentProvider } from '@fluentui-contrib/react-themeless-provider';
import { webLightTheme, Button } from '@fluentui/react-components';

const themeSheet = createCSSStyleSheetFromTheme(':root', webLightTheme);
document.head.adoptedStyleSheets = [...document.adoptedStyleSheets, themeSheet];

// This `CSSStyleSheet` acts as a sentinel for inserting Griffel styles.
// Griffel styles are inserted after `insertionPoint`.
const insertionPoint = new CSSStyleSheet();
const root = createRoot({ insertionPoint });

// These styles are not generated by Griffel.
const nonGriffelStyles = new CSSStyleSheet();
nonGriffelStyles.insertRule('.my-style-from-outside-fluent: { color: red; }');

// Griffel styles will be inserted after `insertionPoint` and
// before `nonGriffelStyles`, allowing styles defined in `nonGriffelStyles`
// to "win" specificity by appearing later in the document order.
const externalStyleSheets = [insertionPoint, nonGriffelStyles];

<ThemelessFluentProvider>
  <root.div styleSheets={externalStyleSheets}>
    <Button className="my-style-from-outside-fluent">Button with external style</Button>
  </root.div>
</ThemelessFluentProvider>;
```
